<template>
	<text class="e-count-num" :style="textStyle">{{ displayValue }}</text>
</template>
<script lang="ts">
	import {
		defineComponent
	} from 'vue'
	import mpMixin from '../../libs/mixin/mpMixin'
	/**
	 * countTo 数字滚动
	 * @description 该组件一般用于需要滚动数字到某一个值的场景，目标要求是一个递增的值。
	 * @tutorial https://www.uviewui.com/components/countTo.html
	 * @property {String | Number}	startVal	开始的数值，默认从0增长到某一个数（默认 0 ）
	 * @property {String | Number}	endVal		要滚动的目标数值，必须 （默认 0 ）
	 * @property {String | Number}	duration	滚动到目标数值的动画持续时间，单位为毫秒（ms） （默认 2000 ）
	 * @property {Boolean}			autoplay	设置数值后是否自动开始滚动 （默认 true ）
	 * @property {String | Number}	decimals	要显示的小数位数，见官网说明（默认 0 ）
	 * @property {Boolean}			useEasing	滚动结束时，是否缓动结尾，见官网说明（默认 true ）
	 * @property {String}			decimal		十进制分割 （ 默认 "." ）
	 * @property {String}			color		字体颜色（ 默认 '#606266' )
	 * @property {String | Number}	fontSize	字体大小，单位px（ 默认 22 ）
	 * @property {Boolean}			bold		字体是否加粗（默认 false ）
	 * @property {String}			separator	千位分隔符，见官网说明
	 * @event {Function} end 数值滚动到目标值时触发
	 * @example <e-count-to ref="uCountTo" :end-val="endVal" :autoplay="autoplay"></e-count-to>
	 */
	export default defineComponent({
		name: "e-count-to",
		mixins: [mpMixin]
	});
</script>
<script lang="ts" setup>
	import {
		ref,
		computed,
		watch,
		toRefs
	} from 'vue'
	import {
		addUnit
	} from '../../libs/utils/_utils'
	import {
		countToProps,
		countToEmits
	} from './props';
	import {
		useDefaultProps
	} from '../../libs/hooks'
	import componentsProps, {
		type DefaultProps
	} from "../../libs/config/componentsProps";

	const {
		countTo: countToDefaultProps
	} = toRefs(componentsProps);

	const _props = defineProps(countToProps)

	const props = useDefaultProps < DefaultProps['countTo'] > (_props, countToDefaultProps);
	const emit = defineEmits(countToEmits)

	const textStyle = computed(() => {
		return {
			fontSize: addUnit(props.value.fontSize as string),
			fontWeight: props.value.bold ? 'bold' : 'normal',
			color: props.value.color
		}
	})

	const formatNumber = (num: string | number) => {
		// 将num转为Number类型，因为其值可能为字符串数值，调用toFixed会报错
		num = Number(num);
		num = num.toFixed(Number(props.value.decimals));
		num += '';
		const x = num.split('.');
		let x1 = x[0];
		const x2 = x.length > 1 ? props.value.decimal + x[1] : '';
		const rgx = /(\d+)(\d{3})/;
		if (props.value.separator && !isNumber(props.value.separator)) {
			while (rgx.test(x1)) {
				x1 = x1.replace(rgx, '$1' + props.value.separator + '$2');
			}
		}
		return x1 + x2;
	}


	const displayValue = ref<string|number>(formatNumber(props.value.startVal as string))

	let localStartVal = ref(props.value.startVal as number)
	let printVal = 0
	// 是否暂停
	let paused = false
	let localDuration = Number(props.value.duration)
	let startTime: number | null = null // 开始的时间
	// let timestamp: number | null = null // 时间戳
	let remaining: number | null = null // 停留的时间
	let rAF: number | undefined = undefined
	let lastTime = 0 // 上一次的时间

	const countDown = computed(() => {
		return (props.value.startVal as number) > (props.value.endVal as number);
	})

	watch(() => props.value.startVal, () => {
		init()
	})
	watch(() => props.value.endVal, () => {
		init()
	})

	const init = () => {
		props.value.autoplay && start();
	}
	const easingFn = (t: number, b: number, c: number, d: number) => {
		return (c * (-Math.pow(2, (-10 * t) / d) + 1) * 1024) / 1023 + b;
	}
	const requestAnimationFrame = (callback: (t: number) => void) => {
		const currTime = new Date().getTime();
		// 为了使setTimteout的尽可能的接近每秒60帧的效果
		const timeToCall = Math.max(0, 16 - (currTime - lastTime));
		const id = setTimeout(() => {
			callback(currTime + timeToCall);
		}, timeToCall);
		lastTime = currTime + timeToCall;
		return id;
	}
	const cancelAnimationFrame = (id: number | undefined) => {
		clearTimeout(id);
	}
	// 开始滚动数字
	const start = () => {
		localStartVal.value = props.value.startVal as number;
		startTime = null;
		localDuration = props.value.duration as number;
		paused = false;
		rAF = requestAnimationFrame(count);
	}

	// 暂定状态，重新再开始滚动；或者滚动状态下，暂停
	const reStart = () => {
		if (paused) {
			resume();
			paused = false;
		} else {
			stop();
			paused = true;
		}
	}
	// 暂停
	const stop = () => {
		cancelAnimationFrame(rAF);
	}


	// 重新开始(暂停的情况下)
	const resume = () => {
		if (!remaining) return
		startTime = 0;
		localDuration = remaining;
		localStartVal.value = printVal;
		requestAnimationFrame(count);
	}
	// 重置
	const reset = () => {
		startTime = null;
		cancelAnimationFrame(rAF);
		displayValue.value = formatNumber(props.value.startVal as number);
	}
	const count = (timestamp: number) => {
		if (!startTime) startTime = timestamp;
		timestamp = timestamp;
		const progress = timestamp - startTime;
		remaining = localDuration - progress;
		if (props.value.useEasing) {
			if (countDown.value) {
				printVal = localStartVal.value - easingFn(progress, 0, localStartVal.value - (props.value.endVal as number), localDuration);
			} else {
				printVal = easingFn(progress, localStartVal.value, (props.value.endVal as number) - localStartVal.value, localDuration);
			}
		} else {
			if (countDown.value) {
				printVal = localStartVal.value - (localStartVal.value - (props.value.endVal as number)) * (progress / localDuration);
			} else {
				printVal = localStartVal.value + ((props.value.endVal as number) - localStartVal.value) * (progress / localDuration);
			}
		}
		if (countDown.value) {
			printVal = printVal < (props.value.endVal as number) ? (props.value.endVal as number) : printVal;
		} else {
			printVal = printVal > (props.value.endVal as number) ? (props.value.endVal as number) : printVal;
		}
		displayValue.value = formatNumber(printVal) || 0;
		if (progress < localDuration) {
			rAF = requestAnimationFrame(count);
		} else {
			emit('end');
		}
	}
	// 判断是否数字
	const isNumber = (val: string) => {
		return !isNaN(parseFloat(val));
	}

	// const destroyed = () => {
	// 	cancelAnimationFrame(rAF);
	// }

	init()
	
	defineExpose({
		reset,
		reStart,
		stop
	})
</script>

<style lang="scss" scoped>
	@import "../../libs/theme/count-to.scss";
</style>
